package dmxGroupControl;

import dmxGroupControl.solvers.*;
import org.jdom2.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;

public abstract class Fixture
        implements Comparable<Fixture>
{
    protected ChannelCollection channels;
    protected DebugTextOptions debugTextOption;
    private ArrayList<Solver> solvers;
    private String name;
    private FixtureType type;

    public Fixture(String name)
    {
        this.name = name;

        channels = new ChannelCollection();
        solvers = new ArrayList<>();

        this.debugTextOption = DebugTextOptions.NONE;
    }

    public Fixture(String name, Element groupEntry)
    {
        this(name);
        Element definition = LightController.GetFixtureDefinition(name);
        if(definition != null)
        {
            type = FixtureType.Parse(definition.getAttributeValue("type"));
        }
        for (Element element : groupEntry.getChildren("solver"))
        {
            solvers.add(Solver.CreateSolver(element, this));
        }
    }

    public FixtureType GetFixtureType()
    {
        return type;
    }

    public void RemoveSolver(String solverType)
    {
        for (Solver solver : solvers)
        {
            if (solver.GetType().compareTo(solverType) == 0)
            {
                solvers.remove(solver);
                return;
            }
        }
    }

    public boolean HasSolver(String solverType)
    {
        for (Solver solver : solvers)
        {
            if (solver.GetType().compareTo(solverType) == 0)
            {
                return true;
            }
        }
        return false;
    }


    public void close()
    {
    }

    public String GetName()
    {
        return name;
    }

    public boolean IsColor()
    {
        return channels.Contains("Red") || channels.Contains("Green")
                || channels.Contains("Blue");
    }

    public boolean IsPanning()
    {
        return channels.Contains("Pan");
    }


    public boolean IsTilting()
    {
        return channels.Contains("Tilt");
    }

    public int GetChannelCount()
    {
        return channels.GetLast().GetDMXChannel() - channels.GetFirst().GetDMXChannel() + 1;
    }

    public ChannelCollection GetChannels()
    {
        return channels;
    }

    public boolean GetIsStrobing()
    {
        return (channels.Contains("Strobe") &&
                channels.GetChannel("Strobe").GetValue() != 0.0f);
    }

    public boolean GetIsSharedStrobe()
    {
        return (channels.Contains("Master")
                && channels.Contains("Strobe")
                && channels.GetChannel("Master").GetDMXChannel() == channels.GetChannel("Strobe").GetDMXChannel());
    }

    public void Set(String name, float value)
    {
        if (channels.Contains(name))
        {
            channels.GetChannel(name).SetValue(value);
        }
        else if (CanSolve(name))
        {
            GetSolverForAttribute(name).Set(name, value);
        }
    }

    public float GetAttribute(String name)
    {
        return GetSolverForAttribute(name).GetAttribute(name);
    }

    public abstract void UpdateDisplay();

    private boolean CanSolve(String attribute)
    {
        return (GetSolverForAttribute(attribute) != null);
    }

    private Solver GetSolverForAttribute(String attribute)
    {
        for (Solver solver : solvers)
        {
            if (solver.SolvesAttribute(attribute))
            {
                return solver;
            }
        }
        return null;
    }

    public boolean CanSet(String attribute)
    {
        return (channels.Contains(attribute) || CanSolve(attribute));
    }

    @Nullable
    protected String GetNameForDMX(int dmx)
    {
        if(channels.stream().anyMatch(channel -> channel.GetDMXChannel() == dmx))
        {
            return channels.stream()
                .filter(channel -> channel.GetDMXChannel() == dmx)
                .findAny()
                .get()
                .GetName();
        }
        else
        {
            return null;
        }
    }

    @Override
    public String toString()
    {
        return GetName();
    }


    @Override
    public int compareTo(@NotNull Fixture o)
    {
        if (this instanceof DMXFixture && o instanceof DMXFixture)
        {
            Integer first = ((DMXFixture) this).GetChannelStart();
            Integer other = ((DMXFixture) o).GetChannelStart();

            return first.compareTo(other);
        }
        else
        {
            return this.toString().compareTo(o.toString());
        }
    }

    public ArrayList<Solver> GetSolvers()
    {
        return solvers;
    }

    public Solver GetSolver(@NotNull String type)
    {
        for (Solver solver : solvers)
        {
            if (solver.GetType().equals(type))
            {
                return solver;
            }
        }
        return null;
    }

    public void SetName(String value)
    {
        this.name = value;
    }

    public void Update()
    {
        if (LightController.GetInstance().AreSolversEnabled())
        {
            for (Solver solver : solvers)
            {
                solver.Solve();
            }
        }
    }

    public static Fixture CreateFixture(@NotNull Element fixtureElement)
    {
        String fixtureType = fixtureElement.getAttributeValue("type");

        switch (fixtureElement.getName())
        {
            case "pixelFixture":
                int xCount = Integer.parseInt(fixtureElement.getAttributeValue("xCount"));
                int yCount = Integer.parseInt(fixtureElement.getAttributeValue("yCount"));
                return new PixelFixture("pixel", fixtureElement, xCount, yCount);
            case "dmxFixture":
                int channel = Integer.parseInt(
                    fixtureElement
                        .getAttributeValue("startChannel")
                );
                return new DMXFixture(fixtureType, fixtureElement, channel);
            default:
                return null;
        }
    }

    public void SetType(FixtureType type)
    {
        this.type = type;
    }
}
